package ddz.db.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;

import java.util.ArrayList;
import java.util.List;

public class RedisLock {

    public static final String UNLOCK_LUA;
    public static final String DEFAULT_LOCK_VALUE = "lock";

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    protected boolean tryLock(String key, String value, long expire) {
        JedisFactory factory = JedisFactory.getFactory();
        JedisCommands jedisCommands = factory.getJedis();
        String result = jedisCommands.set(key, value, "NX", "PX", expire);
        factory.close(jedisCommands);
        return "OK".equals(result);
    }

    protected void lock(String key, String value, long expire) {
        while (!tryLock(key, value, expire)) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
    }

    /**
     * <b>Description:</b>
     * <pre>
     *   释放锁的时候，有可能因为持锁之后方法执行时间大于锁的有效期，此时有可能已经被另外一个线程持有锁，所以不能直接删除
     * </pre>
     *
     * @param key
     * @param value
     * @return
     * @since JDK 1.8
     */
    protected boolean unLock(String key, String value) {
        List<String> keys = new ArrayList<>();
        keys.add(key);
        List<String> args = new ArrayList<>();
        args.add(value);
        JedisFactory factory = JedisFactory.getFactory();
        JedisCommands jedisCommands = factory.getJedis();
        Long result = 0l;
        // 使用lua脚本删除redis中匹配value的key，可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
        // spring自带的执行脚本方法中，集群模式直接抛出不支持执行脚本的异常，所以只能拿到原redis的connection来执行脚本
        if (jedisCommands instanceof JedisCluster) {// 集群模式
            JedisCluster cluster = (JedisCluster) jedisCommands;
            result = (Long) cluster.eval(UNLOCK_LUA, keys, args);
        } else if (jedisCommands instanceof Jedis) {// 单机模式
            Jedis jedis = (Jedis) jedisCommands;
            result = (Long) jedis.eval(UNLOCK_LUA, keys, args);
        }
        factory.close(jedisCommands);
        return result > 0;
    }

    public void lock(String key, long expire) {
        this.lock(key, DEFAULT_LOCK_VALUE, expire);
    }

    public void unlock(String key) {
        this.unLock(key, DEFAULT_LOCK_VALUE);
    }

}
